#ifndef _SHELL_FUNCTIOn_H_
#define _SHELL_FUNCTION_H_

#include <utils/string.h>
#include <utils/function.h>


namespace shell
{

namespace detail
{

class abstract_command
{
public:

  virtual int operator ()(int argc, char** argv) = 0;
  virtual abstract_command* clone(void) = 0;
};

template <typename... _Args>
class command : public abstract_command {};

template <>
class command<>
    : public abstract_command
{
public:

  command(utils::function<int(void)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 0)
    {
      Serial.print("Expect 0 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function();
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(void)> _function;
};

template <typename _Type0>
class command<_Type0>
    : public abstract_command
{
public:

  command(utils::function<int(_Type0)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 1)
    {
      Serial.print("Expect 1 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function(utils::convert<_Type0>(argv[0]));
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(_Type0)> _function;
};

template <typename _Type0, typename _Type1>
class command<_Type0, _Type1>
    : public abstract_command
{
public:

  command(utils::function<int(_Type0, _Type1)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 2)
    {
      Serial.print("Expect 2 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function(
        utils::convert<_Type0>(argv[0]),
        utils::convert<_Type1>(argv[1]));
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(_Type0, _Type1)> _function;
};

template <typename _Type0, typename _Type1, typename _Type2>
class command<_Type0, _Type1, _Type2>
    : public abstract_command
{
public:

  command(utils::function<int(_Type0, _Type1, _Type2)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 3)
    {
      Serial.print("Expect 3 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function(
        utils::convert<_Type0>(argv[0]),
        utils::convert<_Type1>(argv[1]),
        utils::convert<_Type2>(argv[2]));
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(_Type0, _Type1, _Type2)> _function;
};

template <typename _Type0, typename _Type1, typename _Type2, typename _Type3>
class command<_Type0, _Type1, _Type2, _Type3>
    : public abstract_command
{
public:

  command(utils::function<int(_Type0, _Type1, _Type2, _Type3)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 4)
    {
      Serial.print("Expect 4 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function(
        utils::convert<_Type0>(argv[0]),
        utils::convert<_Type1>(argv[1]),
        utils::convert<_Type2>(argv[2]),
        utils::convert<_Type3>(argv[3]));
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(_Type0, _Type1, _Type2, _Type3)> _function;
};

template <typename _Type0, typename _Type1, typename _Type2, typename _Type3, typename _Type4>
class command<_Type0, _Type1, _Type2, _Type3, _Type4>
    : public abstract_command
{
public:

  command(utils::function<int(_Type0, _Type1, _Type2, _Type3, _Type4)> const& fn)
    : _function(fn) {}

  int operator ()(int argc, char** argv)
  {
    if (argc != 5)
    {
      Serial.print("Expect 5 arguments, but get ");
      Serial.println(argc);
      return -1;
    }
    return _function(
        utils::convert<_Type0>(argv[0]),
        utils::convert<_Type1>(argv[1]),
        utils::convert<_Type2>(argv[2]),
        utils::convert<_Type3>(argv[3]),
        utils::convert<_Type4>(argv[4]));
  }

  abstract_command* clone(void)
  { return new command(*this); }

private:

  utils::function<int(_Type0, _Type1, _Type2, _Type3, _Type4)> _function;
};

} // detail

class command
{
public:

  template <typename... _Args>
  command(utils::function<int(_Args...)> function)
    : _function(new detail::command<_Args...>(function)) {}

  command(command const& fn)
    : _function(fn._function->clone()) {}

  ~command(void) { delete _function; }

  int operator ()(int argc, char** argv)
  { return _function->operator ()(argc, argv); }

private:

  detail::abstract_command* _function;
};

} // shell

#endif // _SHELL_FUNCTION_H_
